package org.polyforms.repository.jpa;

import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.SingularAttribute;

import org.polyforms.repository.spi.IdentifierHelper;
import org.springframework.util.ReflectionUtils;

/**
 * Implementation of {@link IdentifierHelper} for JPA 2.0.
 * 
 * @author Kuisong Tong
 * @since 1.0
 */
public final class Jpa2IdentifierHelper implements IdentifierHelper {
    @SuppressWarnings("unchecked")
    private final Map<Class<? extends Member>, ValueGetter> valueGetters = new HashMap<Class<? extends Member>, ValueGetter>();

    private final EntityManager entityManager;

    /**
     * Create an instance with {@link EntityManager}.
     */
    public Jpa2IdentifierHelper(final EntityManager entityManager) {
        this.entityManager = entityManager;
        valueGetters.put(Field.class, new FieldValueGetter());
        valueGetters.put(Method.class, new PropertyValueGetter());
    }

    /**
     * {@inheritDoc}
     */
    public String getIdentifierName(final Class<?> entityClass) {
        return getIdentifierType(entityClass).getName();
    }

    /**
     * {@inheritDoc}
     */
    public Class<?> getIdentifierClass(final Class<?> entityClass) {
        final EntityType<?> entityType = getEntityType(entityClass);
        return entityType.getIdType().getJavaType();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public Object getIdentifierValue(final Object entity) {
        final Member member = getIdentifierType(entity.getClass()).getJavaMember();
        return valueGetters.get(member.getClass()).getValue(member, entity);
    }

    private <T> EntityType<T> getEntityType(final Class<T> entityClass) {
        return entityManager.getMetamodel().entity(entityClass);
    }

    private SingularAttribute<?, ?> getIdentifierType(final Class<?> entityClass) {
        return getEntityType(entityClass).getId(getIdentifierClass(entityClass));
    }

    private interface ValueGetter<T extends Member> {
        /**
         * Get value from specific object using member like {@link Field} or {@link Method}.
         */
        Object getValue(T member, Object target);
    }

    private static final class PropertyValueGetter implements ValueGetter<Method> {
        /**
         * {@inheritDoc}
         */
        public Object getValue(final Method member, final Object target) {
            return ReflectionUtils.invokeMethod(member, target);
        }
    }

    private static final class FieldValueGetter implements ValueGetter<Field> {
        /**
         * {@inheritDoc}
         */
        public Object getValue(final Field member, final Object target) {
            return ReflectionUtils.getField(member, target);
        }
    }
}
